using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Web;


namespace Library.OrmEnterpriseEntity {
	///<summary>
	/// Implement entity transaction
	///</summary>
	public sealed class TransactionManager : ITransaction {
		private TransactionManager(LinkedList<TransactionManager> pools) {
			_connection = null;
			_isDispose = false;
			_isOpen = false;
			_transaction = null;
			_events = new EventHandlerList();
			_pools = pools;
		}

		private const string Key = "Library.OrmEnterpriseEntiry.SqlClient.Key";
		private DbTransaction _transaction;
		private DbConnection _connection;
		private bool _isOpen;
		private bool _isDispose;
		private readonly LinkedList<TransactionManager> _pools;
		internal DbConnection CurrentConnection {
			get { return _connection; }
		}

		internal DbTransaction CurrentTransaction {
			get { return _transaction; }
		}
		internal bool HasTransaction {
			get { return _connection != null; }
		}
		[ThreadStatic]
		private static LinkedList<TransactionManager> _transactionPools;


		//[ThreadStatic]
		//private static TransactionManager _currentTran;
		internal static TransactionManager Current {
			get {
				LinkedList<TransactionManager> trans;

				var context = HttpContext.Current;
				if (context != null) {
					trans = context.Items[Key] as LinkedList<TransactionManager>;

					if (trans == null) {
						trans = new LinkedList<TransactionManager>();
						HttpContext.Current.Items[Key] = trans;
					}
				} else {
					trans = _transactionPools;
					if (trans == null) {
						trans = new LinkedList<TransactionManager>();
						_transactionPools = trans;
					}

				}
				if (trans.Count == 0) {
					var tran = new TransactionManager(trans);
					trans.AddLast(tran);
					return tran;
				}

				return trans.Last.Value;
			}
			/*get
			 * {
				TransactionManager tran;
				var context = HttpContext.Current;
				if (context != null) {
					tran = context.Items[Key] as TransactionManager;

					if (tran == null) {
						tran = new TransactionManager();
						HttpContext.Current.Items[Key] = tran;
					}
				} else {
					//LocalDataStoreSlot slot = Thread.GetNamedDataSlot(Key);
					//tran = Thread.GetData(slot) as TransactionManager;
					tran = _currentTran;
					if (tran == null) {
						tran = new TransactionManager();
						//Thread.SetData(slot, tran);
						_currentTran = tran;
					}
				}

				return tran;
			}*/
		}
		/// <summary>
		/// Get transaction at the top pool
		/// </summary>
		/// <returns></returns>
		public static ITransaction GetCurrentTran() {
			TransactionManager current = Current;
			if (current != null) {
				if (current._isOpen && !current._isDispose) {
					return current;
				}
			}

			return null;
		}
		/// <summary>
		/// if transaction eixsted, return wrapper for current transaction, otherwise create new transaction
		/// </summary>
		/// <returns></returns>
		public static ILocalTransaction CreateLocalTrans() {
			return CreateLocalTrans(Factor.DbContext.Instance.Context.DefaultLevel);
		}
		/// <summary>
		/// if transaction eixsted, return wrapper for current transaction, otherwise create new transaction
		/// </summary>
		/// <param name="isolationLevel"></param>
		/// <returns></returns>
		public static ILocalTransaction CreateLocalTrans(IsolationLevel isolationLevel) {
			var transaction = GetCurrentTran();
			if (transaction == null) {
				return new LocalTransaction(CreateTransaction(isolationLevel));
			}

			return new LocalTransaction(null);
		}
		/// <summary>
		/// Create transaction
		/// </summary>
		/// <returns></returns>
		public static ITransaction CreateTransaction() {
			return CreateTransaction(Factor.DbContext.Instance.Context.DefaultLevel);
		}
		/// <summary>
		/// Create transaction
		/// </summary>
		/// <param name="isolationLevel"></param>
		/// <returns></returns>
		public static ITransaction CreateTransaction(IsolationLevel isolationLevel) {
			TransactionManager current = Current;
			if (current._isOpen && !current._isDispose) {
				throw new Exception("Transaction exits, create new transaction fail");
			}
			var connectionManager = ConnectionManager.Current;
			if (connectionManager.Connection != null) {
				current._connection = connectionManager.Connection;
				connectionManager.TransactionManager = current;
			} else {
				current._connection = Factor.DbContext.Instance.Context.CreateConnection();
				current._connection.Open();
			}
			current._transaction = current._connection.BeginTransaction(isolationLevel);
			current._isOpen = true;
			current._isDispose = false;
			return current;
		}
		/// <summary>
		/// Create new transaction and put it to top transaction pool. This will be a current transaction for after connection
		/// </summary>
		/// <returns></returns>
		public static ITransaction CreateNewTransaction() {
			return CreateNewTransaction(IsolationLevel.ReadCommitted);
		}
		/// <summary>
		/// Create new transaction and put it to top transaction pool. This will be a current transaction for after connection
		/// </summary>
		/// <param name="isolationLevel"></param>
		/// <returns></returns>
		public static ITransaction CreateNewTransaction(IsolationLevel isolationLevel) {
			TransactionManager current = Current;
			TransactionManager next;
			if (current.HasTransaction)
				next = new TransactionManager(current._pools);
			else next = current;

			next._connection = Factor.DbContext.Instance.Context.CreateConnection();
			next._connection.Open();
			next._transaction = next._connection.BeginTransaction(isolationLevel);
			next._isOpen = true;
			next._isDispose = false;
			if (!ReferenceEquals(current, next))
				current._pools.AddLast(next);
			return next;
		}
		/// <summary>
		/// commit top transaction in the pool
		/// </summary>
		public static void CommitCurrentTransaction() {
			TransactionManager current = Current;
			/*if (current._isDispose) {
				throw new ObjectDisposedException("Connection is disposed");
			}

			//	lock (SyncRoot) {
			lock (current) {
				if (current._isOpen) {
					current._transaction.Commit();
					current._connection.Close();
					current._transaction.Dispose();
					current._isOpen = false;
					current._connection = null;
					current._transaction = null;
					var handler = (EventHandler)current.Events[EventTranCommited];
					if (handler != null) {
						handler(current, EventArgs.Empty);
					}

					RemoveAllHandler(current.Events);
				}
			}*/
			current.Commit();
		}

		private static void RemoveAllHandler(EventHandlerList events) {
			var commitHandler = (EventHandler)events[EventTranCommited];
			if (commitHandler != null)
				foreach (EventHandler d in commitHandler.GetInvocationList()) {
					commitHandler -= d;
				}
			events[EventTranCommited] = null;
			var rollbackHandler = (EventHandler)events[EventTranRollbacked];
			if (rollbackHandler != null)
				foreach (EventHandler d in rollbackHandler.GetInvocationList()) {
					rollbackHandler -= d;
				}
			events[EventTranRollbacked] = null;

		}
		/// <summary>
		/// Roll back top transaction in the pool
		/// </summary>
		public static void RollbackCurrentTransaction() {
			TransactionManager current = Current;
			current.RollBack();
			/*
			if (current._isDispose) {
				throw new ObjectDisposedException("Connection is disposed");
			}

			//lock (SyncRoot) {
			lock (current) {
				if (current._isOpen) {
					current._transaction.Rollback();
					current._connection.Close();
					current._transaction.Dispose();
					current._isOpen = false;
					current._connection = null;
					current._transaction = null;
					current._isDispose = true;
					var handler = (EventHandler)current.Events[EventTranRollbacked];
					if (handler != null) {
						handler(current, EventArgs.Empty);
					}

					RemoveAllHandler(current.Events);
				}
			}
			 * */
		}

		#region ITransaction Members
		/// <summary>
		/// Commit current transaction
		/// </summary>
		public void Commit() {
			//CommitCurrentTransaction()
			//var current = this;

			if (_isDispose) {
				throw new ObjectDisposedException("Connection is disposed");
			}

			//	lock (SyncRoot) {
			lock (this) {
				if (_isOpen) {
					_transaction.Commit();
					if (!ReferenceEquals(ConnectionManager.Current.TransactionManager, this))
						_connection.Close();
					else {
						ConnectionManager.Current.TransactionManager = null;
					}
					_transaction.Dispose();
					_isOpen = false;
					_connection = null;
					_transaction = null;
					var handler = (EventHandler)_events[EventTranCommited];
					if (handler != null) {
						handler(this, EventArgs.Empty);
					}

					RemoveAllHandler(_events);
					_pools.Remove(this);
				}
			}
		}
		/// <summary>
		/// Roll back current transaction
		/// </summary>
		public void RollBack() {
			//RollbackCurrentTransaction();
			if (_isDispose) {
				throw new ObjectDisposedException("Connection is disposed");
			}

			//lock (SyncRoot) {
			lock (this) {
				if (_isOpen) {
					_transaction.Rollback();
					if (!ReferenceEquals(ConnectionManager.Current.TransactionManager, this))
						_connection.Close();
					else {
						ConnectionManager.Current.TransactionManager = null;
					}
					_transaction.Dispose();
					_isOpen = false;
					_connection = null;
					_transaction = null;
					_isDispose = true;
					var handler = (EventHandler)_events[EventTranRollbacked];
					if (handler != null) {
						handler(this, EventArgs.Empty);
					}

					RemoveAllHandler(_events);
					_pools.Remove(this);
				}
			}
		}

		private static readonly object EventTranCommited = new object();
		private static readonly object EventTranRollbacked = new object();
		private readonly EventHandlerList _events;
		/// <summary>
		/// Fire after transaction commited
		/// </summary>
		public event EventHandler TranCommited {
			add {
				_events.AddHandler(EventTranCommited, value);
			}
			remove {
				_events.RemoveHandler(EventTranCommited, value);
			}
		}
		/// <summary>
		/// Fire after transaction roll back
		/// </summary>
		public event EventHandler TranRollbacked {
			add {
				_events.AddHandler(EventTranRollbacked, value);
			}
			remove {
				_events.RemoveHandler(EventTranRollbacked, value);
			}
		}

		#endregion

		#region IDisposable Members
		/// <summary>
		/// if object is disposed, do nothing, otherwise rollback transaction
		/// </summary>
		public void Dispose() {
			if (!_isDispose) {
				RollBack();
				_isDispose = true;
			}
		}

		#endregion


		private struct LocalTransaction : ILocalTransaction {

			private ITransaction _trans;
			public void Dispose() {
				RollBack();
			}

			public LocalTransaction(ITransaction transaction) {
				_trans = transaction;
			}


			public void Commit() {
				if (_trans != null) {
					lock (_trans) {
						_trans.Commit();
						_trans = null;
					}
				}
			}

			public void RollBack() {
				if (_trans != null) {
					lock (_trans) {
						_trans.RollBack();
						_trans = null;
					}
				}
			}
		}
	}
}
